ParticleSwarm::ParticleSwarm( const char* input_file ) : Algorithm( input_file ) {
	number_of_particles = 100;
	
	P1 = 0.1;
	P2 = 0.2;
	P3 = 0.7;
}

ParticleSwarm::~ParticleSwarm(){
	for( int i = 0; i < number_of_particles; ++i ){
		delete particles[i];
	}
	delete [] particles;
	delete gBest;	
}

void ParticleSwarm::solve(){
	
	//initialize particles
	particles = new Particle*[number_of_particles];
	for (int i = 0; i < number_of_particles; ++i) {
		particles[i] = new Particle(k,L,sets);
	}
	
	//initialize gBest with particles[0]
	gBest = new int[k];
	updategBestTo(particles[0]);
	
	time_t start = time(NULL);
	do {
		
		for (int i = 0; i < number_of_particles; ++i) {
			Particle *p = particles[i];
			
			int prob[4] = {0, P1*100, (P1+P2)*100, (P1+P2+P3)*100};
			
			for (int j = 0; j < k; ++j ) {
				
				//choose between remain at the same velocity or change it according to pBest or gBest
				int choice[3] = {p->v[j], minus(p->pBest[j], p->x[j]), minus(gBest[j], p->x[j])};
				
				int r = rand()%100+1;
				
				for (int l = 0; l < 3; ++l) {
					if (r >= prob[l] && r <= prob[l+1]) {
						p->v[j] = choice[l];
						break;
					}
				}
				if (times(j,p)) {
					p->calculateFitness();
				}
			}
		}
		
		for (int i = 0; i < number_of_particles; ++i) {
			if (particles[i]->fitness > particles[i]->fitness_pBest) {
				particles[i]->updatepBestPosition();
			}
			if (particles[i]->fitness > fitness_gBest) {
				updategBestTo(particles[i]);
			}
		}
		
	} while ((time(NULL) - start < 60));
}

int ParticleSwarm::pickANewSet(Particle* p) {
	
	int is_in_solution[L];
	memset(is_in_solution,0,L*sizeof(int));
	
	for (int j = 0; j < k; ++j)
		is_in_solution[p->x[j]] = 1;
			
	int sets_to_be_inserted[L];
	for (int l = 0; l < L; ++l) {
		sets_to_be_inserted[l] = l;
	}
	
	for (int l = 0; l < L; ++l) {
		swap(&sets_to_be_inserted[l], &sets_to_be_inserted[rand()%L]);
	}
	
	for (int l = 0; l < L; ++l) {
		if (!is_in_solution[sets_to_be_inserted[l]]) {
			return sets_to_be_inserted[l];
		}
	}
}

int ParticleSwarm::minus(int a,int b) {
	return (a == b) ? 1 : 0;
}

bool ParticleSwarm::times(int i, Particle* p) {
	if (p->v[i] == 1) {
		return false;
	} else {
		p->x[i] = pickANewSet(p);
		return true;
	}
}

void ParticleSwarm::updategBestTo(Particle* p) {
	for (int i = 0; i < k; ++i) {
		gBest[i] = p->x[i];
	}
	fitness_gBest = p->fitness;
}

int ParticleSwarm::getQualityOfBest(){
	return fitness_gBest;
}